package it.okkam.webid.certificate.model;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;

public class GenerateCertificate {
	private PrivateKey privateKey;
	private X509Certificate certificate;
	private ArrayList<String> uris;
	String fullName, O, OU,  CN;
	
	public GenerateCertificate (){
		
	}
	@SuppressWarnings("rawtypes")
	public GenerateCertificate(PrivateKey privateKey, X509Certificate certificate) throws GenerateCertificateException {
		this.privateKey = privateKey;
		this.certificate = certificate;
		this.uris = new ArrayList<String>();

		try {
			for(Object sanObject : certificate.getSubjectAlternativeNames()) {
				if(sanObject instanceof List && ((List)sanObject).size() >= 2) {				
					List sanObjectList = (List)sanObject;

					if(sanObjectList.get(0).equals(GeneralName.uniformResourceIdentifier))
						this.uris.add(sanObjectList.get(1).toString());
				}
			}
		} catch(CertificateParsingException e) {
			throw new GenerateCertificateException("Couldn't read SubjectAlternativeNames", e);
		}
	}

	public PrivateKey getPrivateKey() {
		return privateKey;
	}

	public X509Certificate getCertificate() {
		return certificate;
	}

	public String[] getUris() {
		return uris.toArray(new String[]{});
	}

	public BigInteger getModulus() {
		if(!(this.certificate.getPublicKey() instanceof RSAPublicKey))
			return null;

		RSAPublicKey rsaPublicKey = (RSAPublicKey)this.certificate.getPublicKey();

		return rsaPublicKey.getModulus();
	}

	public BigInteger getPublicExponent() {
		if(!(this.certificate.getPublicKey() instanceof RSAPublicKey))
			return null;

		RSAPublicKey rsaPublicKey = (RSAPublicKey)this.certificate.getPublicKey();

		return rsaPublicKey.getPublicExponent();
	}
	/**
	 * generates private and public key pairs
	 * @param keySize the size of the key
	 * @return
	 * @throws GenerateCertificateException
	 */
	public  KeyPair generateRsaKeyPair(int keySize) throws GenerateCertificateException {
		initBouncyCastleProvider();

		KeyPairGenerator keyPairGenerator;

		try {
			keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
		} catch (Exception e) {
			throw new GenerateCertificateException("Couldn't access rsa key pair generator", e);
		}

		keyPairGenerator.initialize(keySize, new SecureRandom());

		return keyPairGenerator.generateKeyPair();		
	}
/**
 * Generate private and public key, together with the webid
 */
	public  GenerateCertificate generate(KeyPair keyPair, String[] uris, Date validFrom, Date validTill) throws GenerateCertificateException {
		initBouncyCastleProvider();

		X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();

		certificateGenerator.setSerialNumber(BigInteger.valueOf(validFrom.getTime()));
		certificateGenerator.setIssuerDN(new X500Principal("CN="+CN+", O="+O+", OU="+OU));
		certificateGenerator.setNotBefore(validFrom);
		certificateGenerator.setNotAfter(validTill);
		certificateGenerator.setSubjectDN(new X500Principal("CN="+fullName+", O="+O+", OU="+OU));
		certificateGenerator.setPublicKey(keyPair.getPublic());
		certificateGenerator.setSignatureAlgorithm("SHA256WithRSAEncryption");

		ArrayList<ASN1Encodable> sanList = new ArrayList<ASN1Encodable>();

		for(String uri : uris)
			sanList.add(new GeneralName(GeneralName.uniformResourceIdentifier, uri));
	//add the webid into the certificate	
	    try {
	    	certificateGenerator.addExtension(
	    			X509Extensions.SubjectAlternativeName,
	    			true,
	    			new GeneralNames(new DERSequence(sanList.toArray(new ASN1Encodable[]{}))));

			return new GenerateCertificate(keyPair.getPrivate(), certificateGenerator.generate(keyPair.getPrivate(), new SecureRandom()));
		} catch (Exception e) {
			throw new GenerateCertificateException("Couldn't generate x509 certificate", e);
		}
	}

	public  GenerateCertificate generate(int keySize, String[] uris, Date validFrom, Date validTill) throws GenerateCertificateException {
		KeyPair keyPair = generateRsaKeyPair(keySize);

		return generate(keyPair, uris, validFrom, validTill);
	}

	public  GenerateCertificate generate(int keySize, String uri, Date validFrom, Date validTill) throws GenerateCertificateException {
		return generate(keySize, new String[] { uri }, validFrom, validTill);
	}

	public  GenerateCertificate generate(String fullName, String O, String OU, String CN,int keySize, String uri, Date validFrom, Date validTill) throws GenerateCertificateException {
		this.fullName = fullName;
		this.O = O;
		this.OU = OU;
		this.CN = CN;		
		return generate(keySize, new String[] { uri }, validFrom, validTill);
	}
	
	
	public static Date createDeltaDate(int days) {
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.DAY_OF_YEAR, days);

		return calendar.getTime();
	}

	private static void initBouncyCastleProvider() {
		if(Security.getProvider("BC") == null)
			Security.addProvider(new BouncyCastleProvider());
	}
}
